
module DrunkenMaster::Routing
  class Router
    def initialize
      @routes = {}
      @plugging_now = []
      @plugging_path = ["ROOT"]
    end

    # Assigns handler to specific path under the root of the website.
    # If handler has method _be_plugged_, then it will be called 
    # instead of plugging the handler.
    def plug handler, path
      chunks = path.split('/')
      expect_normalized_chunks chunks
      nest path do
        chunks = File.join(@plugging_path).split('/')
        if handler.respond_to?(:be_plugged) &&
            !@plugging_now.include?(handler)
          @plugging_now << handler
          handler.be_plugged(self)
          @plugging_now.pop
        else
          plug_handler handler, chunks
        end
      end
      handler
    end
    
    def nest path
      @plugging_path << path
      raise 'No block' unless block_given?
      yield
      @plugging_path.pop
    end
    
    # Assigns handler to given path that was .split('/')
    def plug_handler handler, path
      @routes[path] ||= []
      @routes[path] << handler
      if handler.respond_to?(:on_plug)
        path = File.join([''] << (path - ["ROOT"]))
        handler.on_plug(self, path)
      end
    end
    
    # Reads @routes hash.
    def routes
      @routes
    end
    
    def handler_for url
      handler_and_tail(url)[0]
    end
    
    def handler_and_tail for_url
      url = for_url.split('/')
      expect_normalized_chunks url
      url[0] = "ROOT"
      tail = []
      while @routes[url] == nil && url.length > 0
        tail = [url.pop] + tail
      end
      matching_routes = ((@routes[url] || []).select { |h|
        !h.respond_to?(:match_tail?) || h.match_tail?(tail.join('/'))
      })      
      if $DEBUG and matching_routes.length > 1
        warn "#{matching_routes.length} routes (#{matching_routes.collect {|r| r.class.to_s}.join(" ")}) matched url '#{url}'."
      end
      if matching_routes.length == 0
        raise "No suitable route found for #{for_url}."
      end
      [matching_routes[0], (tail || [])]      
    end
    
  private
    
    def expect_normalized_chunks chunks
      i ||= 0
      chunks.each do |ch|
        if (i == 0 && ch != "") ||
           (ch == '..') || 
           (ch == '' && i > 0) ||
           (ch == nil)
          raise "Non normalized path format #{chunks.inspect}."
        end
        i += 1
      end
    end
  end
end